Jetty6 Trial and Exploration

A few months ago I was using Jetty5 as my main servlet container and I found it excellent.
I have now ported my application to Jetty6 and while doing so I discovered a few details that I want to share with you all. For all of you asking "Who is using it ?" you can have a look at the reference page.

Let me define what I am aiming to:

We will see that it is possible to do this, but it is not as self evident as one may wish.

What you need to run Jetty6

The first thing is to download Jetty6 from the main Jetty Website Jetty6 Logo. You also need an updated Java Java Logo. Make sure that you are running the wanted JVM by typing

java -version

Once you have unzipped everything you can give Jetty a try as it is , just to make sure that it is working. I will not cover this part since it is already well explained in the Downloading and Installing guide. At this point you should have Jetty running.

Starting Jetty Embedded

Next step is to start your favorite java editor and start coding.
I am skipping the part that sets up an application and handles the loading of all jars that should be loaded in the classpath.

The first bit of code is the one that starts the server and apply the standard configuration. Note that this is not a whole working program, they are snippets to show you how to do things. There is something like the following in the main Documentation

import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.*;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.xml.XmlConfiguration;

    try
      {
      jettyServer=new Server();
      XmlConfiguration configuration = new XmlConfiguration(new File("jetty.xml").toURI().toURL());
      configuration.configure(jettyServer);
      }
    catch ( Exception exc )
      {
      stat.log.exceptionShow(classname+"startServer()",exc);
      }

Adding your own Context

Before going in the nitty details I really need to clarify a few things for you. This will make our life easier. If you read Jetty documentation you will discover that jetty consider almost everything a handler. Have a look at the reasonably good introduction to the architecture and you discover that there are many Handlers and that Servlets are Handlers and also Contexts are Handlers, at the end you come with the idea that you can use everything everywhere, but this is not the case.

Try to forget that everything is a Handler and lets describe the architecture step by step, what follows is a representation of the basic hierarchy of things you get when you start a standard server.

org.mortbay.jetty.Server
    org.mortbay.jetty.handler.HandlerCollection, collection of things that Server should scan
        org.mortbay.jetty.handler.ContextHandlerCollection, another collection of things to scan Hashed by context
        org.mortbay.jetty.handler.DefaultHandler, code that provides basic message if nothing match
        org.mortbay.jetty.handler.RequestLogHandler, code that generates a request log

The above means that you create a Server Object, then you attach to it a HandlerCollection that has inside a Empty ContextHandlerCollection, after it there is a DefaultHandler and finally a RequestLogHandler.

If you read the documentation for HandlerCollection it says that all "handlers" in the Collection will be called and so you wonder why you do not always get an error message !, the answer is simple, DefaultHandler has some code inside it that checks if the request is already handled, and if it is it does nothing. You may already have guessed but it is now evident that the order in which you put things is relevant.

I found difficult to understand what are the classes that just group other stuff, so far what I have found is

Once you have the boxes where to put stuff in, you need the actual stuff to put in. Here things are complicated by the many layers, that are surely useful but sometimes not easy to understand. Again, although it seems that everythign can be put anywhere it is not so.

As far as I understood all context defined by all ContextHandlerCollection go at the same level in the virtual host. I may be wrong, meaning that it is actually possible to have nested ContextHandlerCollection but so far my experiments do not shows so. So, when you plan you configuration remember to create flat context instead of a tree ones.

Another peculiarity is the ResourceHandler, as far as I know It can only go into a HandlerList, so if you need to serve static content from within a ContextHandlerCollection you can add org.mortbay.jetty.servlet.DefaultServlet to that context and this provides static content together with the servlets you put into it.

ServletHandler really does little by itself, it needs a ServletHolder and the actual servlet to do something, bear in mind this when you put things together. Also, remembar is that althougt it is possibile to put a servlet in any of the boxes above the most useful place is a ContextHandlerCollection

Bevare of utility classes

Using the above classes it is quite long to add a servlet to a HendlerCollection, so Jetty developers have written custom classes and methods that makes things simpler. The idea is good but it messes up your brain when you are trying to understand the architecture. The following classes and methods to not ADD anything to the logic, they are used to "simplify" the conding !

I have not dugg into Jetty6 enough to unserstand if the model cannot be made simpler, in any case, let me show you some code.

Getting hold of the standard ContextHandlerCollection

The standard jetty.xml defines a default structure for the services, here is the snippet of the code.

    <Set name="handler">
      <New id="Handlers" class="org.mortbay.jetty.handler.HandlerCollection">
        <Set name="handlers">
         <Array type="org.mortbay.jetty.Handler">
           <Item>
             <New id="Contexts" class="org.mortbay.jetty.handler.ContextHandlerCollection"/>
           </Item>
           <Item>
             <New id="DefaultHandler" class="org.mortbay.jetty.handler.DefaultHandler"/>
           </Item>
           <Item>
             <New id="RequestLog" class="org.mortbay.jetty.handler.RequestLogHandler"/>
           </Item>
         </Array>
        </Set>
      </New>
    </Set>

In my java code I wanted to get hold of the object created and I wanted to retrieve the ContextHandlerCollection so I could add some Contexts to it. It is not difficult to do it, the only thing I could say is that you cannot rely on the Id being assigned but you should use the Class.
jettyServer is the result of Server creation and this code is called after the server is initialized with the standard configuration above.

    rootHandler = (HandlerCollection)jettyServer.getHandler();
    ContextHandlerCollection contesti = (ContextHandlerCollection)rootHandler.getChildHandlerByClass(ContextHandlerCollection.class);

Adding more context to the retrieved handler

This is the meaty part, I wish to ad more context and servlets to the above. It is simple, once you know it. The following code adds a servlet that you can access using http://127.0.0.1/utils/dump to the webserver. Note that the use of the utility class Context makes things really simple but it is confusing if you do not know that it is really an utility class !

    // First you have to create a Context for the servlet and add this to the parent.
    Context context = new Context(contesti,"/utils");

    // Needed to allow servlets to find classes, also needed for beanshell
    context.setClassLoader(getClass().getClassLoader());

    // All servlets have access to stat
    context.setAttribute(ServletCommon.ATTRIBUTE_Stat,stat);

    // Then you add this servlet, you can reach it using /utils/dump
    // If you had put /* then It would have been the default for this context.
    context.addServlet("damiano.ammi.websrv.servlets.DumpServlet", "/dump");

Setting the correct classloader is essential for the system to work properly. If you do not have a clear understanding of what the classloader does it is vital that you study about it.

Adding a virtual host

Adding a virtual host is really simple, all boils down to the use of setVirtualHosts method. The code below adds a virtual host to the "contesti" container. Note that the setVirtualHost is only available within ContextHandler and not in HandlerCollection or HandlerList

    ....
    // this simple line adds the new "container" to the contesti one
    contesti.addHandler(newImagesVirtualHost());
    ....

  private final ContextHandler newImagesVirtualHost ()
    {
    // First of all let me find out where the images actually are.
    String resourceBase = "/somewhere/inthe/filesystems";

    // Let me make a context to handle the following images.
    ContextHandler context = new ContextHandler();

    // This context is bound to this virtual host.
    context.setVirtualHosts(new String []{"images."+domainName});

     // the resources are rooted at / in the above virtual host.
    context.setContextPath("/");

    // set where the actual files are (may not be needed)
    context.setResourceBase(resourceBase);

    // Add a handler for resources, files and the like.
    ResourceHandler files = new ResourceHandler();
    files.setResourceBase(resourceBase);   // this is needed
    context.addHandler(files);

    return context;
    }

Serving both servlets and static content

The Jetty wizard around would say that this is easy, yes, it is if you stick to the standard config but for some reasons I had the following requirements

From the above it follows that I need to use a Context container that I fill up with the servlets. What I would like is to add a ResourceHandler to it, but I cannot, so I have to add a org.mortbay.jetty.servlet.DefaultServlet that will provide static content. The end result is the following.
  private final ContextHandler newWebVirtualHost ()
    {
    Context context = new Context();

    context.setVirtualHosts(new String []{"www."+domainName});

     // If you want no name then just a slash, othervise something like /context/*
    context.setContextPath("/");  // I just need a virtual host, not a context, here.

    // Set where the resuorces can be found.
    String resourceBase = (String)stat.config.webProperty.getValue(KEY_html_dir);
    context.setResourceBase(resourceBase);

    addServletsToContext(context);

    return context;
    }

  private final void addServletsToContext ( Context context  )
    {
    // Needed to allow servlets to find classes, also needed for beanshell
    context.setClassLoader(getClass().getClassLoader());

    // All servlets have access to stat
    context.setAttribute(ServletCommon.ATTRIBUTE_Stat,stat);

    try
      {
      /**
       * The issue: being able to add "custom" servlets to the engine depending on
       * the customer, AmmiJ and TOnezza being the first two, but many more.
       * Also the issue is common to the rest of the system !
       * The solution: a beanshell script adds the components needed on the fly !
       * If things are properly set it is almost like writing Java.
       */
      Interpreter inter = new Interpreter();
      inter.set("stat",stat);
      inter.set("context",context);    // Add the servlets to this context.
      inter.source("config.java");
      }
    catch ( Exception exc )
      {
      stat.log.exceptionShow(classname+"beanshell",exc);
      }

    // Let me try this way to serve static stuff
    context.addServlet("org.mortbay.jetty.servlet.DefaultServlet","/");
    }

Some of you may wonder what is the beanshell stuff, quite simply it is a way to configure the system without hardcoding stuff, something like the jetty/xml configuration, but it is way simpler. As an example let me show you the config.java file that attach a few servlets to the above context.

import damiano.ammi.websrv.servlets.*;
import clienti.tonezza.websrv.*;

System.out.println("Aggiunta Servlets per server Tonezza");

try
  {
  webstat=new TonezzaWebStat();
  webstat.webUtils = new TonezzaWebUtils(stat);
  context.setAttribute(ServletCommon.ATTRIBUTE_WebStat,webstat);

  context.addServlet("clienti.tonezza.websrv.servlets.Index","/index.html");     // just in case someone tryes index.html
  context.addServlet("clienti.tonezza.websrv.servlets.Index","/*");              // This is when nothing is specified

  context.addServlet("clienti.tonezza.websrv.servlets.StoriaTop","/StoriaTop");
  context.addServlet("clienti.tonezza.websrv.servlets.StoriaDetail","/StoriaDetail");

  context.addServlet("clienti.tonezza.websrv.servlets.HotelTop","/HotelTop");
  context.addServlet("clienti.tonezza.websrv.servlets.HotelDetail","/HotelDetail");

  context.addServlet("clienti.tonezza.websrv.servlets.DosiTop","/DosiTop");
  context.addServlet("clienti.tonezza.websrv.servlets.DosiDetail","/DosiDetail");

  context.addServlet("clienti.tonezza.websrv.servlets.ManifTop","/ManifTop");
  context.addServlet("clienti.tonezza.websrv.servlets.ManifDetail","/ManifDetail");

  context.addServlet("clienti.tonezza.websrv.servlets.SportTop","/SportTop");
  context.addServlet("clienti.tonezza.websrv.servlets.SportDetail","/SportDetail");

  context.addServlet("clienti.tonezza.websrv.servlets.AmbieTop","/AmbieTop");
  context.addServlet("clienti.tonezza.websrv.servlets.AmbieDetail","/AmbieDetail");

  context.addServlet("clienti.tonezza.websrv.servlets.Scrivici","/Scrivici");
  }
catch ( Exception exc )
  {
  stat.log.exceptionShow("Aggiunta servlets per Tonezza",exc);
  }

I wish that instead of xml files we where using simple Java or even Javascript to configure Jetyy. It would be clearer and simpler (no need to invent id to refer to objects)

As an example, assume you wish to add a new Context to the ContextHandlerCollection to handle a new virtual domain, using beanshell results in this code. (contesti is the instance of ContextHandlerCollection as retrieved from the java code and it is passed as "environment" object into BeanShell)

  // New Context that handles servlets with context path / and attached to the main COntextHandlerCollection
  mrtg = new Context(contesti,"/");
  //  Set the virtual host for the newly created context
  mrtg.setVirtualHosts(new String []{"mrtg.engidea.com"} );
  // Defines where resources are
  mrtg.setResourceBase("/home/damiano/mrtg/www");
  // Add a servlet that provides static content to it.
  mrtg.addServlet("org.mortbay.jetty.servlet.DefaultServlet","/*");

Classloading a WAR applications

One of the main points of using Jetty is that you can customize it to provide extra services that are not normally available. As an example let's say that you wish one of your servlets to reach a serial port that is in your server.

You do not want your servlet to deal with the messing up of opening ports and so on, you wish to have a simple class that you "pass" to the servlet and that can be used to do what you want.

When you try to do this you go head on with the classloading mechanism that is normally a firewall between your server and the servlet. I am now showing you how to have your server and your servlet a bit closer.

private WebAppContext newWebAppWeb () throws IOException
    {
    WebAppContext webapp = new WebAppContext();

    webapp.setContextPath("/");

    webapp.setWar(new File(jettyHomeDir,"/myspecialapplication.war").getCanonicalPath());

    webapp.setDefaultsDescriptor(new File(jettyHomeDir,"/etc/webdefault.xml").getCanonicalPath());

    WebAppClassLoader loader = new WebAppClassLoader(stat.getClassLoader(),webapp);

    webapp.setClassLoader(loader);

    webapp.setAttribute("SystemStat",stat);

    return webapp;
    }

The above method can be used to generate a webapp that has a classloader being able to load classes from the standard classloader of your application. It also passes an object holding whatever is needed for your servlet to work, you can get the object back using the getAttribute method !

You can attach the above handler using

      rootHandler = (HandlerCollection)jettyServer.getHandler();
      rootHandler.setHandlers(new Handler[]{newWebAppWeb(),newRequestLogHandler()});

At the moment it is all, I will change this page when I find some more details.

You can write to me just adding a message and sending it
If you want to be contacted remember to insert an email address

   

Last Updated 24/04/2009

Home Engidea Home Circle